In case you need a "break" from an xsl:for-each loop

By  Dimitre Novatchev
 
Language  XSLT
Category designpatterns, xml, msxml, html
Posted 28  Feb  2001
Updated 06  Mar  2001
 
Summary
This is an example of a coding technique, necessary to simulate in XSLT the "break" (out of loop) statement of traditional programming languages.
 
This has become really a FAQ.
 
Imagine you're inside an xsl:for-each loop and the node-list to be processed is very long (hundreds or thousands of nodes).
 
At the same time you know that during some point of the processing you'll be able to determine that all output generation has been already performed and there's absolutely no need to repeat the loop for the rest of the nodes. This could happen even when we have processed just the first node from the node-list.
 
In conventional programming languages there's the C-like "break;" statement, which terminates and jumps out of the loop immediately.
 
The question everybody seems to ask is: "What is the XSLT element, which is equivalent to the C 'break;' statement?".
 
And the answer is discouraging:
"None"...
 
This snippet presents a way to simulate "break;" functionality in XSLT.
 
Let's start with a simple problem (submitted by Michael Lee in January in the xslt-list
http://sources.redhat.com/ml/xsl-list/2001-01/msg00151.html
 
"I am writing a XSLT stylesheet to transform a simple table from HTML to WML. However, the "columns" attribute is required for the "table" element in the latter format. Therefore, I must be able to determine the maximum number of cells in the rows and use it as the value for the "columns" attribute."
 
A simple approach is to sort all rows according to the descending number of their cells and then take the first node in the sorted nodelist -- its number of cells is the value of the "columns" attribute.
 
The following code does exactly this:
 
<xsl:template name="maxCols">
  <xsl:for-each select="//tr">
    <xsl:sort select="count(td)" order="descending" />
    <xsl:if test="position() = 1">
      <xsl:value-of select="." />
    </xsl:if>
  </xsl:for-each>
</xsl:template>
 
This is a classical case where a "break" statement would be very useful -- the repeated processing produces the necessary result on the very first iteration -- then the time for the rest is just wasted.
 
Because there isn't a xsl:break element in XSLT, we will have to simulate it.
 
The solution, as originally described by Jeni Tennison, is not to use an xsl:for-each construct at all, but to replace it with a (recursively applied) template:
 
<xsl:template name="maxCols">
  <xsl:apply-templates select="//tr[1]" mode="maxCols" />
</xsl:template>

<xsl:template match="tr" mode="maxCols">
  <xsl:variable name="next"
    select="following-sibling::tr[count(td) &gt; count(current()/td)][1]" />
  <xsl:choose>
    <xsl:when test="$next">
      <xsl:apply-templates select="$next" mode="maxCols" />
    </xsl:when>
    <xsl:otherwise><xsl:value-of select="count(td)" /></xsl:otherwise>
  </xsl:choose>
</xsl:template>
 
What is the underlying logic here?
 
The template with mode="maxCols" is applied initially only on the first "tr" element in the xml document. It constructs $next -- a node-set of all remaining "tr" elements that still have a greater number of "td" children than the current node.
 
The (same) template is re-applied only in case $next is not empty and there really is still work to be done.
 
In case there are no "tr" elements with greater number of "td" children, then the result is immediately output and this is the end of processing.
 
Exactly like "break", isn't it?
 
Code